package com.zeyu.framework.core.configuration;

import com.zeyu.framework.core.persistence.annotation.MyBatisDao;
import com.zeyu.framework.core.persistence.entity.BaseEntity;
import com.zeyu.framework.utils.mybatis.SpringBootVFS;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.MyBatisSystemException;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.entity.Config;
import tk.mybatis.mapper.mapperhelper.MapperHelper;

import javax.sql.DataSource;

/**
 * MyBatis官方提供了mybatis-spring-boot-starter，但是该配置的可以控制的地方太少,目前是自定义配置
 * Note:
 * 1. @EnableTransactionManagement, 等同于<tx:annotation-driven/>
 * 2. 分页的Mybatis方法前调用PageHelper.startPage静态方法即可，紧跟在这个方法后的第一个Mybatis查询方法会被进行分页
 * 获取第2页，5条内容，默认查询总数count
 * PageHelper.startPage(2, 5);
 * 显示为List,实际是Page对象
 * List<User> userList = userMapper.findAll();
 * 用PageInfo对结果进行包装
 * PageInfo<User> page = new PageInfo<>(userList);
 * Created by zeyuphoenix on 16/6/24.
 */
@Configuration
@EnableTransactionManagement     // 开启事务支持,自动注入事务管理器,使用只需要在service加入@Transactional
public class MybatisConfiguration implements TransactionManagementConfigurer, EnvironmentAware {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(MybatisConfiguration.class);

    /**
     * 配置信息前缀
     */
    private static final String CONFIG_PREFIX = "mybatis.";

    // ================================================================
    // Fields
    // ================================================================

    @Autowired
    private DataSource dataSource;

    // 读取配置信息
    private RelaxedPropertyResolver propertyResolver;

    // ================================================================
    // Constructors
    // ================================================================

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    @Override
    public void setEnvironment(Environment environment) {
        this.propertyResolver = new RelaxedPropertyResolver(environment,
                CONFIG_PREFIX);
    }

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean() {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 扫描方式
        bean.setVfs(SpringBootVFS.class);
        // 扫描entity包 使用别名
        bean.setTypeAliasesPackage(propertyResolver.getProperty("type-aliases-package"));
        // 防止扫描错误,指定父类
        bean.setTypeAliasesSuperType(BaseEntity.class);

        bean.setConfigLocation(new DefaultResourceLoader().getResource(
                propertyResolver.getProperty("config-location")));

        //添加XML目录
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            bean.setMapperLocations(resolver.getResources(propertyResolver.getProperty("mapper-locations")));
            return bean.getObject();
        } catch (Exception e) {
            logger.error("init mybatis sql session factory bean error: ", e);
            throw new MyBatisSystemException(e);
        }
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    @Override
    @ConditionalOnMissingBean
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * 对于单表操作,没必要写sql,我们通过Mapper自动生成增删改查的方法
     */
    @Bean
    public MapperHelper initAutoMapper(SqlSessionFactory sqlSessionFactory) {
        MapperHelper mapperHelper = new MapperHelper();

        /**
         *  UUID：设置生成UUID的方法，需要用OGNL方式配置，不限制返回值，但是必须和字段类型匹配
         *  IDENTITY：取回主键的方式，可以配置的内容看下一篇如何使用中的介绍
         *  ORDER：<selectKey>中的order属性，可选值为BEFORE和AFTER
         *  catalog：数据库的catalog，如果设置该值，查询的时候表名会带catalog设置的前缀
         *  schema：同catalog，catalog优先级高于schema
         *  seqFormat：序列的获取规则,使用{num}格式化参数，默认值为{0}.nextval，针对Oracle，可选参数一共4个，对应0,1,2,3分别为SequenceName，ColumnName, PropertyName，TableName
         *  notEmpty：insert和update中，是否判断字符串类型!=''，少数方法会用到
         *  style：实体和表转换时的规则，默认驼峰转下划线，可选值为normal用实体名和字段名;camelhump是默认值，驼峰转下划线;uppercase转换为大写;lowercase转换为小写
         *  enableMethodAnnotation：可以控制是否支持方法上的JPA注解，默认false
         */
        //特殊配置
        Config config = new Config();
        //设置配置
        mapperHelper.setConfig(config);
        // 注册自己项目中使用的通用Mapper接口，这里没有默认值，必须手动注册
        mapperHelper.registerMapper(Mapper.class);
        //配置完成后，执行下面的操作
        mapperHelper.processConfiguration(sqlSessionFactory.getConfiguration());
        //如果没有注册过接口，就注册默认的Mapper接口
        mapperHelper.ifEmptyRegisterDefaultInterface();

        return mapperHelper;
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    /**
     * MyBatis扫描接口
     */
    @Configuration
    @AutoConfigureAfter(MybatisConfiguration.class) //注意，由于MapperScannerConfigurer执行的比较早，所以必须有下面的注解
    public static class MyBatisMapperScannerConfig implements EnvironmentAware {


        // ================================================================
        // Fields
        // ================================================================
        // 读取配置信息
        private RelaxedPropertyResolver propertyResolver;

        // ================================================================
        // Methods from/for super Interfaces or SuperClass
        // ================================================================

        @Override
        public void setEnvironment(Environment environment) {
            this.propertyResolver = new RelaxedPropertyResolver(environment,
                    CONFIG_PREFIX);
        }

        // ================================================================
        // Public or Protected Methods
        // ================================================================

        @Bean
        public MapperScannerConfigurer mapperScannerConfigurer() {
            MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
            mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
            mapperScannerConfigurer.setBasePackage(propertyResolver.getProperty("type-handlers-package"));
            mapperScannerConfigurer.setAnnotationClass(MyBatisDao.class);

            return mapperScannerConfigurer;
        }
    }

    // ================================================================
    // Test Methods
    // ================================================================

}
